package iodevice;
import java.util.LinkedList;
import java.util.List;

import boardData.MainEngine;
import de.htw.saarland.stl.*;

/**
 * Der Kommandoparser empf�ngt Kommandos, wertet diese aus und leitet entsprechende
 * Schritte ein um die Kommandos ausf�hren zu k�nnen.
 * 
 * @author Fabian Hewer
 * @version V1.0.0.001 (28.9.'11)
 */
public class Kdoparser{
	//Klassenvariablen
	private static boolean initialized = false;
	private static boolean finished = false;
	private static boolean moveTest = true; //TODO TEST!!!
	private static LinkedList commandList;
	
	//Klassenkonstanten
	private static final String ENGINEID = "Schachengine K�nigsmord";
	private static final String ENGINEAUTHOR = "Projektgruppe Schachprojekt 2011";
	private static final String MOVEREGEX = "([a-h][1-8][a-h][1-8][rbnq]{0,1})|0000";
	private static final String STARTFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 1"; //Fenstring f�r die Startposition am anfang des Spiels.
	private static final int KDOSUCCESS = 0;
	private static final int KDOUNOWN = 1;
	private static final int KDOFAILURE = 2;
	private static final int KDOQUIT = 3;
	private static final int KDOSENCELESS = 4;
	private static final int OPTIONCOUNT = 10; //TODO Anzahl der Engineoptionen festlegen!
	
	/**
	 * Methode um den KDOPArser zu starten.
	 * Startet die kdoRecieve Methode die ein Kommando empf�ngt und auswertet.
	 */
	public void start(LinkedList commandList){//TODO later dann start(List list)...
		try{
		while (!finished){
			Kdoparser.commandList =commandList;//(LinkedList) list;
			kdoReceive();
		}	
		}catch(Throwable e){
			giveOrder("quit");
		}
	}

	/**
	 * Methode die die Kommandos Empf�ngt und in Kommando und Parameter aufteilt
	 * und die Ausf�hrende Methode (execute()) ausf�hrt.
	 */
	private static void kdoReceive() {
		//Methodenvariablen
		String kdoline = null;
		String kdo;
		String parameter;
		String[] kdoArray;
		boolean successful = false;
		boolean kdoReceived = false;
		
		//Methodenstart
		while (!kdoReceived) {
			kdoline = Stdin.readlnString(); //Lesen einer Zeile und abschneiden der f�hrenden und folgenden Leerzeichen
			if ( kdoline == null || kdoline.trim().isEmpty()) {
			} else
				kdoline = kdoline.trim();
				kdoReceived = true;
		}
		while (!successful) {
			if (kdoline == null){
				break; //TODO Test
				}
			kdoArray = firstWord(kdoline);
			kdo = kdoArray[0];
			parameter = kdoArray[1];
			int returncode = kdoExecute(kdo, parameter); //Werte:
			if (returncode == KDOSUCCESS) {			//Erfolg
				successful = true;
			} else if (returncode == KDOUNOWN) { 	//Unbekanntes Kdo (erstes Token als Kdo)
				kdoline = parameter;		//nach UCI wird das n�chste Wort als Kommando interpretiert.	   
			} else if (returncode == KDOFAILURE) { 	//Fehlerhaftes Kdo (Fehlermeldung)
				CommandPhalanx.infoString("Fehler aufgetreten"); //information an GUI das ein Fehler auftrat.
				//TODO Fehlerbehandlung
				successful = true; //TODO sp�tere fehlerbehandlung.
				finished = true; //TODO sp�tere fehlerbehandlung.
				giveOrder("quit");
			} else if (returncode == KDOQUIT) { 	//"Quit" kdo (Herunterfahren der Engine)
				CommandPhalanx.infoString("Und Tschüss");
				successful = true;
				finished = true;
				giveOrder("quit");
			} else if (returncode == KDOSENCELESS) {		//Kod ignoriert (Sinnlose Eingabe)
				successful = true;
			}
		}
	}

	/**
	 * Methode um f�r eingegebene Kommandos entsprechende Operationen einzuleiten.
	 * @param kdo Kommando das ausgef�hrt werden soll.
	 * @param parameter Parameter des Kommandos
	 * @return Wert der �ber den Erfolg des Kommandos informiert:
	 * 			kdoSuccess = Kdo erfolgreich ausgef�hrt.
	 * 			kdoUnown = Kdo ist unbekannt.
	 * 			kdoFailure = Kdo zwar bekannt aber fehlerhaft.
	 * 			kdoQuit = Quit Kommando gegeben.
	 * 			kdoFailure = Kdo ignoriert, da das Auftreten keinen Sinn macht.
	 */
	private static int kdoExecute(String kdo, String parameter) {
		//Methoden Variablen.
		int returncode;
		// TODO Auto-generated method stub
		if (!initialized) {
			if (kdo.equals("uci")) {
				CommandPhalanx.id(ENGINEID, ENGINEAUTHOR);
				CommandPhalanx.uciok();
				initialized = true;
				return KDOSUCCESS;
			} else if (kdo.equals("quit")) {
				giveOrder(kdo);
				return KDOQUIT;
			} else {
				return KDOUNOWN;
			}
		} else {
			//TODO Kdo Operation...
			if (kdo.equals("uci")) {
				return KDOSENCELESS;
			} else if (kdo.equals("board")) {
				giveOrder("board");
				return KDOSUCCESS;
			} else if (kdo.equals("debug")) {
				String debugMode = firstWord(parameter)[0];
				if (debugMode.equals("on")) {
					giveOrder("debugon");
				} else if (debugMode.equals("off")) {
					giveOrder("debugoff");
				} else {
					return KDOFAILURE;
				}
				return KDOSUCCESS;
			} else if (kdo.equals("isready")) {
				giveOrder(kdo);
				CommandPhalanx.readyok();
				return KDOSUCCESS;
			} else if (kdo.equals("setoption")) {
				returncode = executeOption(parameter);
				//TODO Parameter -> Optionen ver�ndern
				return returncode;
			} else if (kdo.equals("ucinewgame")) {
				giveOrder(kdo);
				return KDOSUCCESS;
			} else if (kdo.equals("position")) {
				returncode = executePosition(parameter);
				return returncode;
			} else if (kdo.equals("go")) {
				returncode = executeGo(parameter);
				return returncode;
			} else if (kdo.equals("stop")) {
				giveOrder(kdo);
				return KDOSUCCESS;
			} else if (kdo.equals("ponderhit")) {
				giveOrder(kdo);
				return KDOSUCCESS;
			} else if (kdo.equals("quit")) {
				giveOrder(kdo);
				return KDOQUIT;
			} else if (kdo.equals("move")) {
				String[] moveArray = new String[2];
				moveArray[0] = "move";
				moveArray[1] = parameter;
				giveOrder(moveArray);
				return KDOSUCCESS;
			} else {
				return KDOUNOWN;
			}
		}
	}

	/**
	 * Spezialisierte Methode zu Ausf�hren des "setoption" Kommandos
	 * @param parameter Parameter des "setoption" Kommandos
	 * @return Wert der �ber den Erfolg des Kommandos informiert:
	 * 			kdoSuccess = Kdo erfolgreich ausgef�hrt.
	 * 			kdoFailure = Kdo fehlerhaft.
	 */
	private static int executeOption(String parameter) {
		String[][] optionArray = new String[OPTIONCOUNT+1][2];
		boolean optionsComplete = false;
		optionArray[0][0] = "option";
		for (int i = 1; i <= OPTIONCOUNT; i++) {
			boolean thisOptionComplete = false;
			while (!optionsComplete && !thisOptionComplete) { //optionsComplete wenn die Zeile zu ende und thisOptionComplete wenn eine Option ins array geschrieben wurde.
				String[] valueArray = firstWord(parameter);
				if (valueArray[0].equals("name")) { //Name der Option
					boolean endOfLine = false;
					StringBuffer optionString = new StringBuffer(10);
					valueArray = firstWord(valueArray[1]);
					do {
						optionString.append(valueArray[0] + " ");
						if (valueArray[1] != null)
							valueArray = firstWord(valueArray[1]);
						else {
							optionArray[i][0] = optionString.toString();
							endOfLine = true;
						}
					} while (!(valueArray[0].equals("name") && valueArray[0].equals("value")) && !endOfLine);
					optionArray[i][0] = optionString.toString();
					if (valueArray[0].equals("value")) { // Wert der Option
						StringBuffer valueString = new StringBuffer(10);
						do {
							valueString.append(valueArray[0] + " ");
							if (valueArray[1] != null)
								valueArray = firstWord(valueArray[1]);
							else {
								optionArray[i][1] = valueString.toString();
								endOfLine = true;
							}
								
						} while (!(valueArray[0].equals("name") && valueArray[0].equals("value")) && !endOfLine);
						optionArray[i][1] = optionString.toString();
					}
				} else {
					return KDOFAILURE;
				}
				if (valueArray[1] == null) {
					optionsComplete = true;
				} else {
					parameter = valueArray[1];
				}
			}
		}
		giveOrder(optionArray);
		return KDOSUCCESS;
	}

	/**
	 * Spezialisierte Methode zum Ausf�hren des "position" Kommandos.
	 * An die Engine wird ein Array weitergeleitet, das im ersten Feld einen Fenstring enth�llt und
	 * im 2. Feld die von der Engine �bergebenen gezogenen Z�ge, die die Engine nach der gegebeben Stellung noch ziehen muss.
	 * werden bei den Tokens Nr 2, 4, 5 ein kleines o �bergeben gab es f�r diese Stellung keine Informationen vom Gui �ber entsprechende Funktionen.
	 * @param parameter Parameter des "position" Kommandos
	 * @return Wert der �ber den Erfolg des Kommandos informiert:
	 * 			kdoSuccess = Kdo erfolgreich ausgef�hrt.
	 * 			kdoFailure = Kdo fehlerhaft.
	 */
	private static int executePosition(String parameter) {
		String[] positionArray = new String[2]; //Hier werden Position und Z�ge eingetregen. Wird an die Recheneinheit geschickt.
		boolean moveComplete = false;
		StringBuffer fen = new StringBuffer(10);
		StringBuffer move = new StringBuffer(10);
		move.append("");
		String[] parameterArray = firstWord(parameter);
		if (parameterArray[0].equals("startpos")) {
			positionArray[0] = STARTFEN;
			if(parameterArray[1] != null)
				parameterArray = firstWord(parameterArray[1]);
		} else if (parameterArray[0].equals("fen")){
			boolean fenEnd = false;
			int fenTokenNr = 1;
			while (!fenEnd) {
				parameterArray = firstWord(parameterArray[1]);
				if (fenTokenNr == 1) { 
					//TokenNr 1 ist die Stellung auf dem Feld
					fen.append(parameterArray[0]); //TODO Abfrage ob �berhaupt ein fenstring...
					fenTokenNr++;
				} else if (fenTokenNr == 2 && parameterArray[0].matches("w|b")){ 
					//TokenNr 2 gibt an welche Seite gerade am Ziehen ist.
					fen.append(" " + parameterArray[0]);
					fenTokenNr++;
				} else if (fenTokenNr == 3 && parameterArray[0].matches("([QKqk]{1,4})|-")) {
					//TokenNr 3 gibt an welche Form der Rochade noch durchgef�hrt werden darf:
					//Q -> Gro�e Rochade Wei� 	 	K -> Kleine Rochade Wei�
					//q -> Gro�e Rochade Schwarz 	k -> Kleine Rochade Schwarz
					fen.append(" " + parameterArray[0]);
					fenTokenNr++;
				} else if (fenTokenNr == 4 && parameterArray[0].matches("([a-h][36])|-")) { 
					//TokenNr 4 gibt an auf welchem Feld ein Bauer steht der jetzt enPassant geschlagen werden k�nnte.
					fen.append(" " + parameterArray[0]);
					fenTokenNr++;
				} else if (fenTokenNr == 5 && parameterArray[0].matches("[0-9]+")) {
					//TokenNr 5 gibt an wieviele Halbz�ge seit dem letzten Schlagen bzw Bauen-ziehen vergangen sind. (50-Z�ge Regel)
					fen.append(" " + parameterArray[0]);
					fenTokenNr++;
				} else if (fenTokenNr == 6 && parameterArray[0].matches("[0-9][0-9]*")) {
					//TokenNr 6 gibt an wieviele Z�ge seit dem Beginn der Partie vergangen sind. Erh�hung nachdem Schwarz gezogen hat.
					fen.append(" " + parameterArray[0]);
					fenTokenNr++;
				} else if (parameterArray[0].equals("moves")) {
					//wenn das Token move ist, ist der Fenstring zu Ende.
					fenEnd = true;
					fenTokenNr++;
				}
				if (parameterArray[1] == null) { //Dann ist die Zeile zu ende.
					fenEnd = true;
					moveComplete = true;
					fenTokenNr++;
				}
			}
			if (fenTokenNr == 3) {  // Auff�llen des Fenstrings
				fen.append(" w o - o o");
			} else if (fenTokenNr == 4) {
				fen.append(" o - o o");
			} else if (fenTokenNr == 5) {
				fen.append(" - o o");
			} else if (fenTokenNr == 6) {
				fen.append(" o o");
			} else if (fenTokenNr == 7) {
				fen.append(" o");
			}
			positionArray[0] = fen.toString(); //Eintragen des Fenstrings in das �bergebene Array.
		} else {
			return KDOFAILURE;
		}
		if (parameterArray[0].equals("moves")) {
			while (!moveComplete) {
				parameterArray = firstWord(parameterArray[1]);
				if (parameterArray[0].matches(MOVEREGEX)) {
					move.append(parameterArray[0] + " ");
				} else {
					return KDOFAILURE;
				}
				if (parameterArray[1] == null) {
					moveComplete = true;
				}
			}
			positionArray[1] = move.toString();
		}
		giveOrder(positionArray);
		return KDOSUCCESS;
	}

	/**
	 * Spezialisierte Methode zum Ausf�hren des "go" Kommandos.
	 * Weiterleitung des Kommandos an Engine durch ein Kommandoarray das immer 12x2 Felder hat.
	 * Das erste Feld ist jeweils der Parameter das zweite Feld enth�llt den Wert.
	 * Z�ge im move-Parameter weden durch Leerzeichen voneinander getrennt.
	 * wurden weniger als 12 parameter �bergeben sind die restlichen Parameter null!!
	 * @param parameter Parameterstring des "go" Kommandos
	 * @return Wert der �ber den Erfolg des Kommandos informiert:
	 * 			kdoSuccess = Kdo erfolgreich ausgef�hrt.
	 * 			kdoFailure = Kdo fehlerhaft.
	 */
	private static int executeGo(String parameter) {
		String[][] goArray = new String[13][2]; //In dieses Array passen alle m�glichen Parameter. Mit den Werten.
		boolean moveComplete = false;
		int tokenNr = 0;
		goArray[0][0] = "go";
		while (parameter != null) {
			String[] tokenArray = firstWord(parameter); //Aufspalten des Parameter Strings in ein Kdo und den Rest
			String[] valueArray;
			String token = tokenArray[0];				//der Parameter.
			String value;								//wert des Parameters.
			if (tokenArray[1] != null) {
				if (token.equals("infinite") || token.equals("ponder")) {
						parameter = tokenArray[1];
						value = "irrelevant"; //Damit da irgendwas steht^^ da "infinite" und "ponder" keine zusatzwerte haben.
				} else {
					if (token.equals("searchmove")){
						StringBuffer moves = new StringBuffer(10);
						while (!moveComplete) {
							valueArray = firstWord(tokenArray[1]);
							if (valueArray[0].matches(MOVEREGEX)) {
								moves.append(" " + valueArray[0]);
								tokenArray[1] = valueArray[1]; //erkannter Zug wird von String getrennt (der Rest wird wieder nach einem Zug untersucht)
							} else {
								moveComplete = true;
								parameter = tokenArray[1]; // das gerade bearbeitete Kommando wird hier von Parameterstring getrennt. (der Rest wird wieder nach einem Kdo untersucht)
							}
							if (valueArray[1] == null) {
								moveComplete = true;
								parameter = tokenArray[1];
							}
						}
						value = moves.toString();
					} else {
					valueArray = firstWord(tokenArray[1]); //Aufspalten des String in Wert f�r aktuelles Kommando und in weitere Kommandos
					value = valueArray[0];
					parameter = valueArray[1]; // das gerade bearbeitete Kommando wird hier von Parameterstring getrennt. (der Rest wird wieder nach einem Kdo untersucht)
					}
				}
			} else {
				if (token.equals("infinite") || token.equals("ponder")) {
				parameter = tokenArray[1];
				value = "irrelevant"; //Damit da irgendwas steht^^ da "infinite" und "ponder" keine zusatzwerte haben.
				} else {
					return KDOFAILURE;
				}
			}
			goArray[tokenNr+1][0] = token;
			goArray[tokenNr+1][1] = value;
			System.out.println(goArray[tokenNr+1][0] + ", " + goArray[tokenNr+1][1]);
			tokenNr++;
		}
		giveOrder(goArray);
		return KDOSUCCESS;
	}
	
	/**
	 * Methode, die ein Array zur�ckliefert, dessen erstes Element das erste Wort des zu 
	 * �bergebenen Strings und dessen zweites Element der Rest des Strings ist.
	 * Worttrennung im Sinne dieser Methode sind Leerzeichen " " und Tabs "\t" angesehen.
	 * @param line Zu entschl�sselnder String.
	 * @return Array: 1. Element = erstes Wort.
	 * 				  2. Element = rest des Strings. (null wenn String nur ein Wort enth�llt.)
	 */
	public static String[] firstWord(String line) {
		int positionSpace = line.indexOf(' ');			//Position des ersten Leerzeichens
		int positionTab   = line.indexOf('\t');          //oder Tabs.
		String[] returnString = new String[2];
		if (!(positionSpace == -1 && positionTab == -1)) {
			if (positionSpace < positionTab || positionTab == -1) {
				returnString[0] = line.substring(0, positionSpace);
				returnString[1] = line.substring(positionSpace).trim();
			} else {
				returnString[0] = line.substring(0, positionTab);
				returnString[1] = line.substring(positionTab).trim();
			}
		} else {
			returnString[0] = line;
			returnString[1] = null;
		}
		returnString[0].trim();
		return returnString;
		}
	
	/**
	 * Metode um ein Objekt der Komandoliste zuzufügen.
	 * @param command das Kommando
	 */
	public synchronized static void giveOrder(Object command) {
		commandList.add(command);
	}
	
	public void stop(){
		finished = true;
	}
}